package com.harmontronics.hanma.paas.paas.domain.app.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.harmontronics.hanma.paas.paas.common.PageBase;
import com.harmontronics.hanma.paas.paas.consts.BaseI18nKeyEnum;
import com.harmontronics.hanma.paas.paas.consts.DbConstants;
import com.harmontronics.hanma.paas.paas.consts.FileTypeConstants;
import com.harmontronics.hanma.paas.paas.consts.ScriptConstants;
import com.harmontronics.hanma.paas.paas.domain.app.entity.HmScript;
import com.harmontronics.hanma.paas.paas.domain.app.entity.valueobject.Fields;
import com.harmontronics.hanma.paas.paas.domain.app.entity.valueobject.I18nKeyEnum;
import com.harmontronics.hanma.paas.paas.domain.app.entity.valueobject.ScriptContentTypeEnum;
import com.harmontronics.hanma.paas.paas.domain.app.entity.valueobject.ScriptTypeEnum;
import com.harmontronics.hanma.paas.paas.domain.app.repository.persistence.HmScriptRepository;
import com.harmontronics.hanma.paas.paas.domain.app.repository.po.HmScriptPO;
import com.harmontronics.hanma.paas.paas.exception.ErrorKeyException;
import com.harmontronics.hanma.paas.paas.token.ComposeTokenKit;
import com.harmontronics.hanma.paas.paas.utils.EscapeUtil;
import com.harmontronics.hanma.paas.paas.utils.RequestUtils;
import groovy.lang.GroovyClassLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;

@Service("hmScriptService")
@DS("#header.App-Unique")
public class HmScriptService {

    @Autowired
    private HmScriptRepository hmScriptRepository;


    @Autowired
    private ComposeTokenKit composeTokenKit;


    @Autowired
    private ScriptExecutor scriptExecutor;


    public HmScript findById(Long id) {
        HmScriptPO po = hmScriptRepository.getById(id);
        return BeanUtil.toBean(po, HmScript.class);
    }

    public void save(HmScript hmScript) {
        validField(null, Fields.NAME, hmScript.getName());
        validField(null, Fields.CODE, hmScript.getCode());
        HmScriptPO po = BeanUtil.toBean(hmScript, HmScriptPO.class);
        hmScriptRepository.save(po);
    }

    public void update(HmScript hmScript) {
        HmScriptPO po = BeanUtil.toBean(hmScript, HmScriptPO.class);
        hmScriptRepository.updateById(po);
    }

    public void remove(List<Long> ids) {
        hmScriptRepository.removeByIds(ids);
    }

    public List<HmScript> getList() {
        List<HmScriptPO> pos = hmScriptRepository.list();
        return Convert.toList(HmScript.class, pos);
    }

    public PageBase<HmScript> getList(String name, String code, int pageNo, int pageSize) {
        Page<HmScriptPO> page = new Page<>(pageNo, pageSize);
        QueryWrapper<HmScriptPO> queryWrapper = new QueryWrapper<>();
        if (!ObjectUtils.isEmpty(name)) {
            String escapeStr = EscapeUtil.escapeChar(name);
            queryWrapper.like(Fields.NAME, escapeStr);
        }
        if (!ObjectUtils.isEmpty(code)) {
            String escapeStr = EscapeUtil.escapeChar(code);
            queryWrapper.like(Fields.CODE, escapeStr);
        }
        queryWrapper.orderByDesc(DbConstants.CREATE_DATE);
        IPage<HmScriptPO> resultPage =
                hmScriptRepository.selectPage(queryWrapper, page);
        return PageBase.from(resultPage, HmScript.class);
    }

    public void validField(Long id, String field, String value) {
        if (Fields.NAME.equals(field)) {
            HmScriptPO po = hmScriptRepository.findByName(value);
            if (po == null) {
                return;
            }
            if (id == null) {
                throw new ErrorKeyException(I18nKeyEnum.SCRIPT_NAME_EXISTS);
            }
            if (po.getId().longValue() != id.longValue()) {
                throw new ErrorKeyException(I18nKeyEnum.SCRIPT_NAME_EXISTS);
            }
        }

        if (Fields.CODE.equals(field)) {
            HmScriptPO po = hmScriptRepository.findByCode(value);
            if (po == null) {
                return;
            }
            if (id == null) {
                throw new ErrorKeyException(I18nKeyEnum.SCRIPT_CODE_EXISTS);
            }
            if (po.getId().longValue() != id.longValue()) {
                throw new ErrorKeyException(I18nKeyEnum.SCRIPT_CODE_EXISTS);
            }
        }
    }


    public String saveScriptFile(byte[] data, String fileType) {
        try {
            if (FileTypeConstants.FILE_GROOVY.equalsIgnoreCase(fileType)) {
                GroovyClassLoader classLoader = new GroovyClassLoader();
                Class clazz = classLoader.parseClass(new String(data));
                Method method = clazz.getMethod("run");
//                int modifiers = method.getModifiers();
//                if (!Modifier.isStatic(modifiers)) {
//                    throw new NoSuchMethodException();
//                }
            }
            if (FileTypeConstants.FILE_JS.equals(fileType)) {
                ScriptEngineManager manager = new ScriptEngineManager();
                ScriptEngine engine = manager.getEngineByName("nashorn");
            }

            File file = getFile(fileType);
            Files.write(file.toPath(), data);
            return file.getName();
        } catch (NoSuchMethodException exception) {
            throw new ErrorKeyException(I18nKeyEnum.SCRIPT_RUN_METHOD_NOT_FOUND);
        } catch (IOException ex) {
            throw new ErrorKeyException(BaseI18nKeyEnum.WRITING_FILE_ERROR);
        }
    }


    private synchronized File getFile(String fileType) {
        ApplicationHome home = new ApplicationHome(getClass());
        File dir = home.getDir();
        File rootDir = new File(dir, ScriptConstants.SCRIPT_FILE_ROOT_DIR);
        if (!rootDir.exists()) {
            rootDir.mkdir();
        }
        String appUnique = RequestUtils.getAppUnique();
        if (ObjectUtils.isEmpty(appUnique)) {
            File file = new File(rootDir, RandomUtil.randomString(RandomUtil.BASE_CHAR, 6) + "." + fileType);
            if (file.exists()) {
                return getFile(fileType);
            }
            return file;
        } else {
            File appDir = new File(rootDir, appUnique);
            if (!appDir.exists()) {
                appDir.mkdir();
            }
            File file = new File(appDir, RandomUtil.randomString(RandomUtil.BASE_CHAR, 6) + "." + fileType);
            if (file.exists()) {
                return getFile(fileType);
            }
            return file;
        }

    }

    private void required(HmScriptPO po) {
        if (po == null) {
            throw new ErrorKeyException(I18nKeyEnum.SCRIPT_NOT_FOUND);
        }
    }


    public Object execute(Long scriptId, Map<String, Object> values) throws IOException {
        HmScriptPO po = hmScriptRepository.getById(scriptId);
        required(po);
        return execute(po, values);
    }

    public Object execute(HmScriptPO po, Map<String, Object> values) throws IOException {
        if (po.getType() != ScriptTypeEnum.GROOVY) {
            throw new UnsupportedOperationException("script type: " + po.getType().name() + " not supported");
        }
        if (po.getContentType() == ScriptContentTypeEnum.CODE) {
            String code = URLDecoder.decode(po.getContent(), "UTF-8");
            return scriptExecutor.execute(code, values);
        }

        String appUnique = composeTokenKit.getAppUnique();

        File scriptFile = getScriptFile(appUnique);
        return scriptExecutor.execute(scriptFile, values);
    }

    public Object execute(String scriptCode, Map<String, Object> values) throws IOException {
        HmScriptPO po = hmScriptRepository.findByCode(scriptCode);
        required(po);
        return execute(po, values);

    }

    private File getScriptFile(String appUnique) {
        ApplicationHome home = new ApplicationHome(getClass());
        File homeDir = home.getDir();
        File scriptHomeDir = new File(homeDir, "script");
        return new File(scriptHomeDir, appUnique);

    }
}